<template>
  <ElForm v-bind="{ ...formProps, ref: _formRef }">
    <ElRow :gutter="props.gutter">
      <ElCol :key="itemComfig.key" :span="itemComfig.span"
        v-for="{ itemComfig, itemVnode, formItemProps } in formItemVNode">
        <ElFormItem v-if="itemComfig.visible" v-bind="{ ...formItemProps }">
          <component v-model="model![itemComfig.key]" :is="itemVnode" />
        </ElFormItem>
      </ElCol>
    </ElRow>
  </ElForm>
</template>

<script lang="ts" setup>
import { computed, h, ref, isVNode, watch, reactive } from "vue";
import type { ComponentPublicInstance, VNode } from "vue";
import { ElForm, ElFormItem, ElRow, ElCol } from "element-plus";
import { formProps as elFormProps } from "element-plus";
import type { FormInstance, FormValidateCallback } from "element-plus";
import { optionComMap } from "./types";
import type { FormItem, FormItemConfig, FormItemOptionType } from "./types";
import { isEqual, omit } from "lodash";

const props = defineProps({
  ...elFormProps,
  /** 表单栅格间隔 */
  gutter: {
    type: Number,
    default: 16,
  },
  /** 表单项配置 */
  formItems: {
    type: Array as () => FormItem[],
    default: () => [],
  },
});

// ElForm 属性配置,从 props 中排除 items 属性，避免传递给 ElForm 组件
const formProps = omit(props, ["formItems", "gutter", "ref"]);

// 表单 Config
const formItemConfigMap = reactive<Map<string, FormItemConfig>>(new Map());
/** 需要重建的 VNode key */
const rebuildVnodeKey = new Set<string>();
/** 表单项 VNode 缓存 */
const formItemVnodeCache = new Map<string, VNode>();
// 表单项配置
const formItemVNode = computed(() => {
  return props.formItems.map((item) => {
    const {
      type,
      props: _props,
      key,
      span = 24,
      slot: itemSlot = {},
      show,
      options: _options,
      ...formItemProps
    } = item;

    // 获取计算后的表单配置
    const { props = {}, visible, options } = formItemConfigMap.get(key) ?? {};

    // 选项（响应式）
    let optionVnode: null | VNode[] = null;
    if (options && !isVNode(type)) {
      // 获取选项组件
      const optionCom = optionComMap[type.name as keyof typeof optionComMap];
      // 如果没有匹配的组件
      if (optionCom) {
        // 如果选项不为空
        if (options.length > 0) {
          // 创建选项VNode
          optionVnode = options.map((item) =>
            h(optionCom, { ...item, key: item.value })
          );
        }
      }
    }

    // 获取缓存的表单项VNode
    let VnodeCache = formItemVnodeCache.get(key) ?? null;
    // 计算是否需要重建
    if ((rebuildVnodeKey.has(key) || !VnodeCache) && visible === true) {
      VnodeCache = h(type, props, { default: () => optionVnode, ...itemSlot });
      // 缓存表单项 VNode
      formItemVnodeCache.set(key, VnodeCache);
    }

    return {
      itemComfig: { key, span, visible, options },
      comProps: { ...props },
      formItemProps: { ...formItemProps, prop: key },
      itemVnode: VnodeCache,
    };
  });
});

// 监听表单项配置变化,以便重置表单
watch(
  () => formItemVNode.value,
  (newValue, oldValue) => {
    // 清空重建标记
    rebuildVnodeKey.clear();
    /** 需要重置的表单项 */
    const resetKey: Set<string> = new Set();
    // 遍历新表单项
    newValue.forEach(({ itemComfig, comProps }, index) => {
      // 解构新属性
      const { visible: newVisible, options: newOptions } = itemComfig;
      const { disabled: newDisabled } = comProps;
      // 结构旧属性
      const { visible: oldVisible, options: oldOptions } =
        oldValue[index].itemComfig;
      const { disabled: oldDisabled } = oldValue[index].comProps;

      // 表单项被隐藏
      if (newVisible === false && newVisible !== oldVisible) {
        resetKey.add(itemComfig.key);
      }

      // 表单想被禁用
      if (newDisabled === true && newDisabled !== oldDisabled) {
        resetKey.add(itemComfig.key);
      }

      // 表单选项改变
      if (!isEqual(oldOptions, newOptions)) {
        resetKey.add(itemComfig.key);
      }
    });

    /** 没有需要重置的表单项 */
    if (resetKey.size === 0) return;

    /** 重置表单 */
    resetFields([...resetKey]);
  },
  { deep: true }
);

/** options 缓存 */
const optionsCache: Map<string, FormItemOptionType[]> = new Map();
// 更新表单项
const updateFormItemsConfig = () => {
  props.formItems.forEach(async (item) => {
    // 解构表单配置对象
    const {
      options: rawOptions,
      key,
      props: rawProps = () => { },
      show: rawShow = () => true,
    } = item;

    // 获取旧表单项配置
    const oldConfig = formItemConfigMap.get(key);

    // 计算 组件 props
    const newProps =
      typeof rawProps === "function" ? rawProps(props.model) : rawProps;

    // 计算组件是否可见
    const visible =
      typeof rawShow === "function" ? rawShow(props.model) : rawShow;

    // 当前表单项没有提供选项 options
    if (!rawOptions) {
      const newConfig = { props: newProps, visible };
      if (!isEqual(oldConfig, newConfig) && visible === true) {
        rebuildVnodeKey.add(key);
      }
      // 设置表单项配置
      formItemConfigMap.set(key, newConfig);
      return;
    }

    // options 是数组
    if (Array.isArray(rawOptions)) {
      const newConfig = { props: newProps, visible, options: rawOptions };
      if (!isEqual(oldConfig, newConfig) && visible === true) {
        rebuildVnodeKey.add(key);
      }
      // 设置表单项配置
      formItemConfigMap.set(key, newConfig);
      return;
    }

    // options 是函数
    if (typeof rawOptions === "function") {
      // 返回结果是 FormItemOptionType[] 或是 异步数据获取对象
      const result = rawOptions(props.model) ?? [];

      // options 执行结果是是数组
      if (Array.isArray(result)) {
        const newConfig = { props: newProps, visible, options: result };
        if (!isEqual(oldConfig, newConfig) && visible === true) {
          rebuildVnodeKey.add(key);
        }
        // 设置表单项配置
        formItemConfigMap.set(key, newConfig);
        return;
      }

      // options 执行结果是是异步函数对象,检查是否有缓存
      const cache = optionsCache.get(result.key);
      if (cache) {
        const newConfig = { props: newProps, visible, options: cache };
        if (!isEqual(oldConfig, newConfig) && visible === true) {
          rebuildVnodeKey.add(key);
        }
        // 使用缓存
        formItemConfigMap.set(key, newConfig);
        return;
      }

      const newConfig = {
        props: { ...newProps, loading: true },
        visible,
        options: [],
      };
      if (!isEqual(oldConfig, newConfig) && visible === true) {
        rebuildVnodeKey.add(key);
      }
      // 先清空用于显示的数据,并设置 loading 状态
      formItemConfigMap.set(key, newConfig);

      // 开始获取选项
      try {
        // 执行获取数据函数
        const res = await result.getOptions(props.model);

        if (Array.isArray(res)) {
          const newConfig = {
            props: { ...newProps, loading: false },
            visible,
            options: res,
          };
          if (!isEqual(oldConfig, newConfig) && visible === true) {
            rebuildVnodeKey.add(key);
          }
          // 设置表单项配置
          formItemConfigMap.set(key, newConfig);
          // 缓存异步数据选项
          optionsCache.set(result.key, res);
        }
      } catch (_) {
        const newConfig = {
          props: { ...newProps, loading: false },
          visible,
          options: [],
        };
        if (!isEqual(oldConfig, newConfig) && visible === true) {
          rebuildVnodeKey.add(key);
        }
        // 停止 loading , 防止有人非要 reject
        formItemConfigMap.set(key, newConfig);
      }
    }
  });
};

// 立即执行一次
updateFormItemsConfig();

// 监听表单数据变化,等待处理异步数据
watch(
  () => props.model,
  () => {
    updateFormItemsConfig();
  },
  { deep: true }
);

// 表单实例
const formRef = ref<FormInstance>();
const _formRef = (instance: Element | ComponentPublicInstance | null) => {
  formRef.value = (instance as FormInstance) || {};
};

/** 验证表单 */
const validate = (callback?: FormValidateCallback) => {
  return new Promise<boolean>((resolve) => {
    formRef.value
      ?.validate(callback)
      .then((valid) => {
        resolve(valid);
      })
      .catch(() => {
        resolve(false);
      });
  });
};
/** 重置表单 */
const resetFields = (props?: string | string[]) => {
  formRef.value?.resetFields(props);
};

/** 刷新表单配置 */
const refreshConfig = () => {
  updateFormItemsConfig();
};

defineExpose({
  /** 验证表单 */
  validate,
  /** 重置表单 */
  resetFields,
  /** 刷新表单配置,用于刷新其他地方异步获取的数据 */
  refreshConfig,
});
</script>
